//
// Created by tang on 22-10-26.
//

#include "cube.hpp"
#include <glm/ext/matrix_transform.hpp>

/*
* Cube:
*         1-----------2
*        /|          /|
*       / |         / |
*      0--+--------3  |
*      |  |        |  |
*      |  5--------+--6
*      | /         | /
*      |/          |/
*      4-----------7
* Axis:
*             y+  z-
*             |  /
*             | /
*             |/
*   x- -------+--------> x+
*            /|
*           / |
*          /  |
*         z+  y-
*/

#define SIDE_LEN 0.52f
#define P0 -(SIDE_LEN), +(SIDE_LEN), +(SIDE_LEN)
#define P1 -(SIDE_LEN), +(SIDE_LEN), -(SIDE_LEN)
#define P2 +(SIDE_LEN), +(SIDE_LEN), -(SIDE_LEN)
#define P3 +(SIDE_LEN), +(SIDE_LEN), +(SIDE_LEN)
#define P4 -(SIDE_LEN), -(SIDE_LEN), +(SIDE_LEN)
#define P5 -(SIDE_LEN), -(SIDE_LEN), -(SIDE_LEN)
#define P6 +(SIDE_LEN), -(SIDE_LEN), -(SIDE_LEN)
#define P7 +(SIDE_LEN), -(SIDE_LEN), +(SIDE_LEN)

#define UV_TL 0.0f, 1.0f    // top left
#define UV_TR 1.0f, 1.0f    // top right
#define UV_BL 0.0f, 0.0f    // bottom left
#define UV_BR 1.0f, 0.0f    // bottom right

constexpr float a = 0.52f;

struct VertexData {
    GLfloat position[3];
    GLfloat uv_coord[2];
};

static const VertexData data[]
{
    // x- side
    {P1, UV_TL},
    {P0, UV_TR},
    {P5, UV_BL},
    {P0, UV_TR},
    {P4, UV_BR},
    {P5, UV_BL},
    
    // x+ side
    {P3, UV_TL},
    {P2, UV_TR},
    {P7, UV_BL},
    {P2, UV_TR},
    {P6, UV_BR},
    {P7, UV_BL},

    // y- side
    {P4, UV_TL},
    {P5, UV_BL},
    {P7, UV_TR},
    {P7, UV_TR},
    {P6, UV_BR},
    {P5, UV_BL},

    // y+ side
    {P0, UV_BL},
    {P1, UV_TL},
    {P2, UV_TR},
    {P0, UV_BL},
    {P2, UV_TR},
    {P3, UV_BR},

    // z- side
    {P1, UV_TR},
    {P2, UV_TL},
    {P5, UV_BR},
    {P2, UV_TL},
    {P6, UV_BL},
    {P5, UV_BR},

    // z+ side
    {P0, UV_TL},
    {P3, UV_TR},
    {P4, UV_BL},
    {P3, UV_TR},
    {P4, UV_BL},
    {P7, UV_BR},
};

void CubeApp::OnInitialize() {
    App::OnInitialize();

    m_program_ = GL::Program::Create("cube");
    m_vao_ = GL::VertexArray::Create(1);

    m_vao_->Bind();
    {
        auto buffer = m_vao_->Get(0);
        buffer->Bind(GL_ARRAY_BUFFER);
        buffer->Data(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW);
        GLCallValidation(glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(VertexData), (void *) 0));
        GLCallValidation(glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(VertexData), (void *) offsetof(VertexData, uv_coord)));
        GLCallValidation(glEnableVertexAttribArray(0));
        GLCallValidation(glEnableVertexAttribArray(1));
    }
    m_vao_->UnBind();

    m_texture_ = GL::Texture::Create("app_logo.png");

    m_draw_per_side_ = false;
    m_enable_auto_rotation_ = true;
}

void CubeApp::OnCleanup() {
    App::OnCleanup();
}
void CubeApp::RenderFrame(double delta_time, double total_time) {

    if (m_enable_auto_rotation_) {
        m_x_angle_ = std::fmod(m_x_angle_ + (float) delta_time * m_x_speed_ * 60.f, 360.f);
        m_y_angle_ = std::fmod(m_y_angle_ + (float) delta_time * m_y_speed_ * 60.f, 360.f);
        m_z_angle_ = std::fmod(m_z_angle_ + (float) delta_time * m_z_speed_ * 60.f, 360.f);
    }
    glm::mat4 mvp =
            glm::rotate(glm::mat4(1.f), glm::radians(m_x_angle_), glm::vec3(1.f, 0.f, 0.f)) *
            glm::rotate(glm::mat4(1.f), glm::radians(m_y_angle_), glm::vec3(0.f, 1.f, 0.f)) *
            glm::rotate(glm::mat4(1.f), glm::radians(m_z_angle_), glm::vec3(0.f, 0.f, 1.f));

    glEnable(GL_DEPTH_TEST);
    // glEnable(GL_BLEND);
    // glBlendFunc(GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR);

    m_program_->Use();
    m_program_->UniformMat4f("mvp", mvp);
    m_vao_->Bind();

    GLCallValidation(glActiveTexture(GL_TEXTURE0));
    GLCallValidation(glBindTexture(GL_TEXTURE_2D, *m_texture_));

    if (m_draw_per_side_)
        GLCallValidation(glDrawArrays(GL_TRIANGLES, m_side_index_ * 6, 6));
    else
        GLCallValidation(glDrawArrays(GL_TRIANGLES, 0, std::size(data)));
    m_program_->NotUse();
}
void CubeApp::RenderConfigUI() {
    App::RenderConfigUI();

    if (ImGui::TreeNodeEx("Cube 设置", ImGuiTreeNodeFlags_Framed)) {
        ImGui::AlignTextToFramePadding();
        ImGui::Columns(2);

        ImGui::TextUnformatted("启用旋转：");
        ImGui::NextColumn();
        ImGui::Checkbox("##AutoRotate", &m_enable_auto_rotation_);
        ImGui::NextColumn();

        ImGui::BeginDisabled(!m_enable_auto_rotation_);
        ImGui::TextUnformatted("X-轴转速：");
        ImGui::NextColumn();
        ImGui::DragFloat("##xSpeed", &m_x_speed_, 0.1f, 0.f, 100.f, "%.1f");
        ImGui::NextColumn();
        ImGui::TextUnformatted("Y-轴转速：");
        ImGui::NextColumn();
        ImGui::DragFloat("##ySpeed", &m_y_speed_, 0.1f, 0.f, 100.f, "%.1f");
        ImGui::NextColumn();
        ImGui::TextUnformatted("Z-轴转速：");
        ImGui::NextColumn();
        ImGui::DragFloat("##zSpeed", &m_z_speed_, 0.1f, 0.f, 100.f, "%.1f");
        ImGui::NextColumn();
        ImGui::EndDisabled();
        ImGui::BeginDisabled(m_enable_auto_rotation_);
        ImGui::TextUnformatted("X-轴角度：");
        ImGui::NextColumn();
        ImGui::DragFloat("##xAngle", &m_x_angle_, 1.f, 0.f, 359.99f, "%.1f");
        ImGui::NextColumn();
        ImGui::TextUnformatted("Y-轴角度：");
        ImGui::NextColumn();
        ImGui::DragFloat("##yAngle", &m_y_angle_, 1.f, 0.f, 359.99f, "%.1f");
        ImGui::NextColumn();
        ImGui::TextUnformatted("Z-轴角度：");
        ImGui::NextColumn();
        ImGui::DragFloat("##zAngle", &m_z_angle_, 1.f, 0.f, 359.99f, "%.1f");
        ImGui::NextColumn();
        ImGui::EndDisabled();
        ImGui::TextUnformatted("独立面");
        ImGui::NextColumn();
        ImGui::Checkbox("##SideRendering", &m_draw_per_side_);
        ImGui::NextColumn();
        ImGui::TextUnformatted("面");
        ImGui::NextColumn();
        ImGui::SliderInt("##SideIndex", &m_side_index_, 0, 5);
        ImGui::NextColumn();
        ImGui::Columns(1);

        // ImGui::Image(*m_texture_);

        ImGui::TreePop();
    }
}

DEFINE_FACTORY_FUNC(CreateCubeApp, win, args) {
    (void) args;
    return std::make_unique<CubeApp>(win, "Cube App");
}

REGISTER_APP_CREATOR("CubeApp", &CreateCubeApp);
